
package com.idealighter.game.games.horserace.manager;

import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import com.idealighter.game.common.CommonMsgMgr;
import com.idealighter.game.core.common.Game;
import com.idealighter.game.core.constant.Operator;
import com.idealighter.game.core.error.ErrorCode;
import com.idealighter.game.dblog.service.DbLogService;
import com.idealighter.game.dictionary.dic.HorceraceLogTypeDic;
import com.idealighter.game.dictionary.dic.HorseLogPlanDic;
import com.idealighter.game.dictionary.dic.HorseRaceRoomDic;
import com.idealighter.game.dictionary.dic.HorseRaceTimeDic;
import com.idealighter.game.dictionary.dic.HorseraceSenceTypeDic;
import com.idealighter.game.dictionary.domain.HorceraceLogTypeDomain;
import com.idealighter.game.dictionary.domain.HorseLogPlanDomain;
import com.idealighter.game.dictionary.domain.HorseRaceRoomDomain;
import com.idealighter.game.event.manager.EventMgr;
import com.idealighter.game.event.struct.ClearGameDataEvent;
import com.idealighter.game.event.struct.PlayerExitEvent;
import com.idealighter.game.event.struct.ShutdownRoomEvent;
import com.idealighter.game.event.struct.StartRoomEvent;
import com.idealighter.game.gamehall.event.EnterRoomEvent;
import com.idealighter.game.gamehall.event.EnterTableEvent;
import com.idealighter.game.gamehall.event.ExitRoomEvent;
import com.idealighter.game.gamehall.event.ExitTableEvent;
import com.idealighter.game.gamehall.event.RoomMemInfoUpdateEvent;
import com.idealighter.game.gamehall.manager.GameHallMgr;
import com.idealighter.game.games.common.AbstractRoom;
import com.idealighter.game.games.common.SeatState;
import com.idealighter.game.games.horserace.struct.AreBet;
import com.idealighter.game.games.horserace.struct.GameStatus;
import com.idealighter.game.games.horserace.struct.HorseRaceRoom;
import com.idealighter.game.games.horserace.struct.HorseRaceSeat;
import com.idealighter.game.games.horserace.struct.HorseRaceTable;
import com.idealighter.game.log.core.LogReason;
import com.idealighter.game.log.struct.game.Game16WinLoseLog;
import com.idealighter.game.log.struct.game.GameAfeeLog;
import com.idealighter.game.login.manager.LoginMgr;
import com.idealighter.game.player.manager.PlayerMgr;
import com.idealighter.game.player.service.bo.PlayerBo;
import com.idealighter.game.player.struct.Player;
import com.idealighter.game.schedule.manager.ScheduleMgr;
import com.idealighter.game.server.ExecutorMgr;
import com.idealighter.game.server.core.executor.DisruptorExecutor;
import com.idealighter.utils.code.RandCodeUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// import com.idealighter.game.dblog.service.DbLogService;

/**
 * 赛马逻辑处理 .
 */
@Singleton
public class HorseRaceMgr {

  private static final Logger LOG = LoggerFactory.getLogger(HorseRaceMgr.class);
  private final EventMgr eventMgr;
  // 游戏线程executor,可以执行普通任务和定时任务
  private final DisruptorExecutor gameExecutor;
  private final HorseRaceTimeDic timeDic;
  private final PlayerMgr playerMgr;
  private final ScheduleMgr scheduleMgr;
  private final CommonMsgMgr commonMsgMgr;
  private final HorseRaceMsgMgr msgMgr;
  private final HorseRaceDataMgr dataMgr;
  private final HorseRaceRoomDic roomDic;
  private final LoginMgr loginMgr;
  private final HorseLogPlanDic horsePlanDic;
  private final HorseraceSenceTypeDic horseraceSenceTypeDic;
  private final HorseRaceControlScript controlScript;
  private final HorceraceLogTypeDic horceLogTypeDic;
  // private final HorseRaceControlScript controlScript;

  /**
   * 构造函数 .
   * 
   * @param eventMgr .
   * @param executorMgr .
   * @param timeDic .
   * @param playerMgr .
   * @param scheduleMgr .
   * @param commonMsgMgr .
   * @param msgMgr .
   * @param dataMgr .
   * @param roomDic .
   * @param gameHallMgr .
   * @param loginMgr .
   * @param horsePlanDic .
   * @param horceLogTypeDic .
   * @param horsePlanTypeDic .
   * @param controlScript .
   * @param horseraceSenceTypeDic .
   */
  @Inject
  public HorseRaceMgr(EventMgr eventMgr, ExecutorMgr executorMgr, HorseRaceTimeDic timeDic,
      PlayerMgr playerMgr, ScheduleMgr scheduleMgr, CommonMsgMgr commonMsgMgr,
      HorseRaceMsgMgr msgMgr, HorseRaceDataMgr dataMgr, HorseRaceRoomDic roomDic,
      GameHallMgr gameHallMgr, LoginMgr loginMgr, HorseLogPlanDic horsePlanDic,
      HorceraceLogTypeDic horceLogTypeDic, HorceraceLogTypeDic horsePlanTypeDic,
      HorseRaceControlScript controlScript, HorseraceSenceTypeDic horseraceSenceTypeDic) {
    this.playerMgr = playerMgr;
    this.scheduleMgr = scheduleMgr;
    this.commonMsgMgr = commonMsgMgr;
    this.msgMgr = msgMgr;
    this.dataMgr = dataMgr;
    this.roomDic = roomDic;
    this.loginMgr = loginMgr;
    this.eventMgr = eventMgr;
    this.timeDic = timeDic;
    this.horsePlanDic = horsePlanDic;
    this.horceLogTypeDic = horceLogTypeDic;
    this.horseraceSenceTypeDic = horseraceSenceTypeDic;
    this.controlScript = controlScript;
    eventMgr.register(this);
    this.gameExecutor = executorMgr.getGameExecutor(Game.HORCERACE.getModuleId());
    gameStart();
  }

  /**
   * 订阅玩家退出游戏事件, 在gameExecutor中执行, 避免线程安全问题 .
   * 
   * @param event .
   */
  @Subscribe
  public void onPlayerExitGame(PlayerExitEvent event) {
    gameExecutor.execute(() -> {
      Player player = event.player;
      long playerId = player.getId();

      HorseRaceSeat seat = dataMgr.getPlayerSeat(playerId);
      if (seat != null) {
        // 玩家在房间中但是没有准备，退出桌子，退出房间
        doExitTable(seat, player);
      }

      // 玩家是否在赛马房间中
      if (player.curRoom instanceof HorseRaceRoom) {
        // 退出房间
        exitRoom(player);
      }
    });
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onStartRoom(StartRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.HORCERACE) {
        roomDic.load();
      }
    });
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onShutDownRoom(ShutdownRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.HORCERACE) {
        roomDic.load();
        AbstractRoom room = dataMgr.getRoom(event.getRoomId());
        for (Long playerId : room.getPlayers()) {
          Player player = playerMgr.getPlayer(playerId);
          if (player != null) {
            loginMgr.noticeLogoutForGameExit(player);
          }
        }
      }

    });
  }

  @Subscribe
  public void onClearGameData(ClearGameDataEvent event) {
    gameExecutor.execute(() -> doClearGameData(event.playerId));
  }

  /**
   * 进入游戏大厅 .
   * 
   * @param player 玩家.
   */
  public void enterGameHall(Player player) {
    if (player.curRoom != null) {
      Game game = player.curRoom.game();
      LOG.warn("[赛马]玩家[{}][{}]在[{}]游戏中，暂时无法进入游戏", player.getId(), player.getPlayerName(),
          game.getDesc());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ALREADY_IN, Operator.SYSTEM,
          game.getDesc());
      return;
    }

    msgMgr.sendEnterGameHallMsg(player);
    LOG.info("[赛马]玩家[{}][{}]进入大厅成功", player.getPlayerName(), player.getId());
  }

  /**
   * 玩家进入房间 .
   * 
   * @param player 玩家.
   * @param roomId .
   */
  public void enterRoom(Player player, int roomId) {
    AbstractRoom curRoom = player.curRoom;
    if (curRoom != null) {
      Game game = curRoom.game();
      LOG.warn("[赛马]玩家[{}][{}]当前在游戏[{}]房间[{}]中不能进入房间", player.getId(), player.getPlayerName(),
          game.getDesc(), curRoom.getId());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ALREADY_IN, Operator.SYSTEM,
          game.getDesc());
      return;
    }

    HorseRaceRoom room = dataMgr.getRoom(roomId);
    if (room == null) {
      LOG.error("[赛马]玩家[{}][{}]进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      return;
    }

    HorseRaceRoomDomain roomDom = roomDic.get(roomId);
    if (roomDom.getIsActive() != 1) {
      LOG.error("[赛马]玩家[{}][{}]进入的房间[{}]已经关闭", player.getPlayerName(), player.getId(), roomId);
      return;
    }
    if (player.getGold() < roomDom.getLower()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_NOT_ENOUGH, Operator.SYSTEM);
      LOG.warn("[赛马]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(), roomId,
          roomDom.getLower());
      return;
    }
    if (player.getGold() > roomDom.getUpper()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD, Operator.SYSTEM);
      LOG.warn("[赛马]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(), roomId,
          roomDom.getUpper());
      return;
    }

    if (!player.vip()
        && room.getPlayers().size() >= roomDom.getMaxNum() * roomDom.getOrdinarPeople() / 100) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_VIP_ROOM_FULL, Operator.SYSTEM);
      LOG.warn("[赛马]玩家[{}][{}]进入的房间[{}]普通玩家人数已超过上限", player.getPlayerName(), player.getId(), roomId,
          roomDom.getMaxNum());
      return;
    }

    if (room.getPlayers().size() >= roomDom.getMaxNum()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ROOM_FULL, Operator.SYSTEM);
      LOG.warn("[赛马]玩家[{}][{}]进入的房间[{}]已超过最大人数[{}]", player.getPlayerName(), player.getId(), roomId,
          roomDom.getMaxNum());
      return;
    }

    // 修改玩家当前房间
    player.curRoom = room;
    room.getPlayers().add(player.getId());
    // 发送进入房间消息
    msgMgr.sendEnterRoomMsg(player);
    LOG.info("[赛马]玩家[{}][{}]进入房间[{}]成功", player.getPlayerName(), player.getId(), roomId);

    // 触发进入房间事件
    eventMgr.post(new EnterRoomEvent(player, room));
  }

  /**
   * 玩家金币兑换筹码 .
   * 
   * @param player 玩家.
   * @param gold 兑换筹码的金币 .
   */
  public void exchangeChips(Player player, long gold) {
    HorseRaceSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[赛马]玩家[{}][{}]不在牌桌中不能兑换筹码", player.getPlayerName(), player.getId());
      return;
    }
    if (player.getGold() < gold
        + convertGold(seat.table.room.getId().intValue(), seat.getTotalChips())) {
      LOG.error("[赛马]玩家[{}][{}]兑换的金币[{}]加上筹码[{}]兑换的金币超过自己的金币总和[{}]", player.getPlayerName(),
          player.getId(), gold, seat.getTotalChips(), player.getGold());
      return;
    }
    if (seat.table.status != GameStatus.REST && seat.table.status != GameStatus.BET) {
      LOG.warn("[赛马]玩家[{}][{}]只有休息和下注阶段才能上分", player.getPlayerName(), player.getId());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_ALLOW_EXCHANGE, Operator.SYSTEM);
      return;
    }

    long chips = convertChip(seat.table.room.getId().intValue(), gold);
    seat.setTotalChips(seat.getTotalChips() + chips);
    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);

    LOG.info("[赛马]玩家[{}][{}]用金币[{}]兑换筹码[{}]成功", player.getPlayerName(), player.getId(), gold,
        chips);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));
  }

  /**
   * 玩家筹码兑换金币 .
   * 
   * @param player 玩家.
   * @param chips .
   */
  public void exchangeGold(Player player, long chips) {
    HorseRaceSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[赛马]玩家[{}][{}]不在牌桌中不能兑换金币", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.getTotalChips() < chips) {
      LOG.error("[赛马]玩家[{}][{}]兑换的筹码[{}]超过自己的筹码总和[{}]", player.getPlayerName(), player.getId(),
          chips, seat.getTotalChips());
      return;
    }
    HorseRaceTable table = seat.table;
    if (table.status != GameStatus.REST) {
      LOG.warn("[赛马]玩家[{}][{}]只有休息阶段才能下分", player.getPlayerName(), player.getId());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_ALLOW_EXCHANGE, Operator.SYSTEM);
      return;
    }

    seat.setTotalChips(seat.getTotalChips() - chips);
    long gold = convertGold(seat.table.room.getId().intValue(), chips);

    // 发送兑换筹码变更
    msgMgr.sendChipsChangeMsg(seat);

    LOG.info("[赛马]玩家[{}][{}]用筹码[{}]兑换金币[{}]成功", player.getPlayerName(), player.getId(), chips,
        gold);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));
  }

  /**
   * 玩家退出房间 .
   * 
   * @param player 玩家.
   */
  public void exitRoom(Player player) {
    AbstractRoom curRoom = player.curRoom;
    HorseRaceSeat seat = dataMgr.getPlayerSeat(player.getId());

    if (curRoom instanceof HorseRaceRoom) {
      if (seat == null) {
        player.curRoom = null;
        HorseRaceRoom room = (HorseRaceRoom) curRoom;
        room.getPlayers().remove(player.getId());
        msgMgr.sendExitRoomMsg(player, 0);

        LOG.info("[赛马]玩家[{}][{}]退出房间[{}]", player.getPlayerName(), player.getId(), room.getId());
        // 触发退出房间事件
        eventMgr.post(new ExitRoomEvent(player, curRoom));
      } else {
        LOG.error("[赛马]玩家[{}][{}]在座位中不能退出房间,请先退出桌子", player.getPlayerName(), player.getId());
        msgMgr.sendExitRoomMsg(player, 1);
      }
    } else {
      LOG.warn("[赛马]玩家[{}][{}]不在房间中，无法退出房间", player.getPlayerName(), player.getId());
    }
  }

  /**
   * 筹码换算金币,只会是多个金币等于一个筹码 .
   * 
   * @param roomId .
   * @param chips .
   */
  public long convertGold(int roomId, long chips) {
    HorseRaceRoomDomain roomDomain = roomDic.get(roomId);

    return chips * roomDomain.getProportionGold() / roomDomain.getProportionChips();
  }

  /**
   * 金币换算筹码 .
   * 
   * @param roomId .
   * @param gold .
   * @return
   */
  public long convertChip(int roomId, long gold) {
    HorseRaceRoomDomain roomDomain = roomDic.map().get(roomId);

    return gold * roomDomain.getProportionChips() / roomDomain.getProportionGold();
  }

  /**
   * 玩家进入牌桌 .
   * 
   * @param player 玩家.
   * @param tableId .
   * @param order .
   */
  public void enterTable(Player player, int tableId, byte order, String tablePwd) {
    if (!(player.curRoom instanceof HorseRaceRoom)) {
      LOG.error("[赛马]玩家[{}][{}]当前不在赛马房间中不能进入赛马桌子", player.getPlayerName(), player.getId());
      return;
    }

    HorseRaceTable table = dataMgr.getTable(tableId);

    if (table == null) {
      LOG.error("[赛马]玩家[{}][{}]进入的牌桌[{}]不存在", player.getPlayerName(), player.getId(), tableId);
      return;
    }

    if (dataMgr.getPlayerSeat(player.getId()) != null) {
      LOG.error("[赛马]玩家[{}][{}]已经在牌桌中不能重复进入牌桌", player.getPlayerName(), player.getId());
      return;
    }

    fastEnterTable(player, table.room.getId().intValue());

  }

  /**
   * 快速进入赛马桌子 .
   * 
   * @param player 玩家.
   * @param roomId .
   */
  public void fastEnterTable(Player player, int roomId) {
    if (!(player.curRoom instanceof HorseRaceRoom)) {
      LOG.error("[赛马]玩家[{}][{}]当前不在赛马房间中不能直接进入桌子", player.getPlayerName(), player.getId());
      return;
    }

    HorseRaceSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat != null) {
      LOG.error("[赛马]玩家[{}][{}]已经在牌桌中不能重复进入牌桌", player.getPlayerName(), player.getId());
      return;
    }

    HorseRaceRoom room = dataMgr.getRoom(roomId);
    if (room == null) {
      LOG.error("[赛马]玩家[{}][{}]快速进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      return;
    }

    HorseRaceSeat emptySeat = findEmptySeat(player, room);
    if (emptySeat != null) {
      doEnterTable(player, emptySeat, null);
    } else {
      // 发送快速进入桌子结果
      msgMgr.sendFastEnterTableMsg(player, 1);
      commonMsgMgr.sendTipDlgMsg(player, ErrorCode.GAME_NOT_SIT, Operator.SYSTEM);
    }
  }

  /**
   * 玩家进入座位 .
   * 
   * @param player 玩家.
   * @param seat 座位信息.
   */
  private void doEnterTable(Player player, HorseRaceSeat seat, String tablePwd) {
    int roomId = seat.table.room.getId().intValue();
    HorseRaceTable table = seat.table;
    HorseRaceRoomDomain roomDom = roomDic.get(seat.table.room.getId().intValue());
    if (player.getGold() < roomDom.getLower()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_NOT_ENOUGH, Operator.SYSTEM);
      LOG.warn("[赛马]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(), roomId,
          roomDom.getLower());
      return;
    }

    if (player.getGold() > roomDom.getUpper()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD, Operator.SYSTEM);
      LOG.warn("[赛马]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(), roomId,
          roomDom.getUpper());
      return;
    }

    // 玩家进桌前的房间设置校验
    // if (!gameHallMgr.checkTableSetting(player, seat.table(), tablePwd)) {
    // return;
    // }

    if (player.getGold() < roomDom.getAfee()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_ENOUGH_FEE, Operator.SYSTEM);
      LOG.warn("[赛马]玩家[{}][{}]进入赛马桌子金币不足台费", player.getPlayerName(), player.getId());
      return;
    } else {
      playerMgr.minusGold(player, roomDom.getAfee(), LogReason.HORSE_RACE_EXIT_TABLE_EXCHANGE_GOLD);
      DbLogService.log(new GameAfeeLog(player, table, roomDom.getAfee()));
    }

    seat.setPlayerId(player.getId());
    seat.setPlayerName(player.getPlayerName());
    seat.setSex(player.getSex());
    seat.setRobot(player.robot());
    seat.setState(SeatState.GAMING);

    // 更新玩家座位
    dataMgr.updatePlayerSeats(player.getId(), seat);
    // 触发进入桌子事件
    eventMgr.post(new EnterTableEvent(player, seat));

    // 向玩家发送进入牌桌结果信息
    int time = (int) table.stepFuture.getDelay(TimeUnit.SECONDS);
    msgMgr.sendEnterTableMsg(player, table);
    msgMgr.sendOtherEnterTableMsg(seat);
    msgMgr.sendHistory(seat);
    msgMgr.sendTableBetInfosMsg(seat);
    msgMgr.sendGameStage(seat, time < 0 ? 5 : time);
    msgMgr.sendMultiple(seat);
    LOG.info("[赛马]玩家[{}][{}]进入桌子[{}]座位[{}]成功", player.getPlayerName(), player.getId(),
        table.getId(), seat.getOrder());

  }

  /**
   * 玩家退出牌桌 .
   * 
   * @param player 玩家.
   */
  public void exitTable(Player player) {
    HorseRaceSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[赛马]玩家[{}][{}]没在牌桌中不能退出牌桌", player.getPlayerName(), player.getId());
      return;
    }

    // 退出桌子
    doExitTable(seat, player);
  }

  /**
   * 退出桌子 .
   * <p/>
   * 1.玩家正常退出牌桌, 能在在线玩家中拿到player, 正常退出桌子
   * <p/>
   * 2.玩家非正常退出(如掉线)后,从活跃玩家或数据库中拿到PlayerDomain，结算后马上更新缓冲和数据库
   * 
   * @param seat 座位信息.
   * @param player 玩家.
   */
  private void doExitTable(HorseRaceSeat seat, Player player) {
    HorseRaceTable table = seat.table;
    if (table.status == GameStatus.GAME) { // 开奖过程中退出，立刻更新玩家金币
      playerMgr.addGold(player, convertGold(table.room.getId().intValue(), seat.billChips),
          LogReason.HORSE_RACE_BILL);
    } else if (table.status == GameStatus.BET) {
      playerMgr.addGold(player, convertGold(table.room.getId().intValue(), seat.totalBets()),
          LogReason.HORSE_RACE_BILL);
    }
    msgMgr.sendExitTableMsg(seat);
    dataMgr.removePlayerSeat(seat.getPlayerId());
    LOG.info("[赛马]玩家[{}][{}]退出桌子[{}]", seat.getPlayerId(), seat.getPlayerName(),
        seat.table.getId());
    seat.clear();
    if (table.status == GameStatus.BET) { // 下注阶段退同步一次筹码
      msgMgr.sendTableBetInfosMsg(table);
    }

    // 触发退出桌子事件
    eventMgr.post(new ExitTableEvent(player, seat));
  }

  /**
   * 开始游戏 .
   */
  private void gameStart() {
    for (HorseRaceRoom room : dataMgr.rooms()) {
      for (HorseRaceTable table : room.tables()) {
        doRest(table);
      }
    }
  }

  /**
   * 休息阶段 .
   * 
   * @param table .
   */
  private void doRest(HorseRaceTable table) {
    table.status = GameStatus.REST;
    int time = timeDic.get(table.status.val).getTime();
    for (HorseRaceSeat seat : table.seats) {
      if (seat.getPlayerId() > 0) {
        playerMgr.noticeGold(playerMgr.getPlayer(seat.getPlayerId()));
      }
      seat.bets.clear();
      seat.billChips = 0;
    }
    msgMgr.sendGameStage(table, time);
    table.reset();
    table.secenId = getSenceId();
    msgMgr.sendSence(table);
    table.stepFuture =
        scheduleMgr.schedule(() -> doBet(table), time, TimeUnit.SECONDS, gameExecutor);
    LOG.info("[赛马]房间[{}]桌子[{}]阶段[{}]", table.room.getId(), table.getId(), table.status.desc);
  }

  /**
   * 下注阶段 .
   * 
   * @param table .
   */
  private void doBet(HorseRaceTable table) {
    table.status = GameStatus.BET;
    randMultiple(table);
    int time = timeDic.get(table.status.val).getTime();
    msgMgr.sendGameStage(table, time);
    table.stepFuture =
        scheduleMgr.schedule(() -> doGame(table), time, TimeUnit.SECONDS, gameExecutor);
    LOG.info("[赛马]房间[{}]桌子[{}]阶段[{}]", table.room.getId(), table.getId(), table.status.desc);

  }

  /**
   * 开奖阶段 .
   * 
   * @param table .
   */
  private void doGame(HorseRaceTable table) {
    table.status = GameStatus.GAME;
    // randArea(table);
    controlReard(table);
    table.horseRank();
    msgMgr.sendHorseDetail(table);
    int time = timeDic.get(table.status.val).getTime();
    msgMgr.sendGameStage(table, time);
    balanceTable(table);
    // msgMgr.sendDealCardsMsg(table);
    // controlScript.controlDealCards(table);
    // balanceTable(table);
    // controlScript.balanceControl(table);
    // table.addHistory();
    table.history.add(table.getHorse(table.horse));
    msgMgr.sendHistory(table);
    table.stepFuture =
        scheduleMgr.schedule(() -> dobalance(table), time, TimeUnit.SECONDS, gameExecutor);
    LOG.info("[赛马]房间[{}]桌子[{}]阶段[{}]", table.room.getId(), table.getId(), table.status.desc);
  }

  /**
   * 结算阶段 .
   * 
   * @param table .
   */
  private void dobalance(HorseRaceTable table) {
    table.status = GameStatus.BALANCE;
    for (HorseRaceSeat seat : table.seats) {
      if (seat.getPlayerId() > 0) {
        doBill(seat);
      }
    }
    int time = timeDic.get(table.status.val).getTime();
    msgMgr.sendGameStage(table, time);
    table.stepFuture =
        scheduleMgr.schedule(() -> doRest(table), time, TimeUnit.SECONDS, gameExecutor);
    LOG.info("[赛马]房间[{}]桌子[{}]阶段[{}]", table.room.getId(), table.getId(), table.status.desc);
  }

  /**
   * 开出结果 .
   */
  public void randArea(HorseRaceTable table) {
    int random = RandCodeUtil.random(horsePlanDic.getTotalWeight());
    int sum = 0;
    // Collections.shuffle(horsePlanDic.list());
    for (HorseLogPlanDomain plan : table.plan) {
      sum += plan.getProbability();
      if (random < sum) {
        table.horse = plan.getAreid();
        break;
      }
    }
  }

  /**
   * .
   * 
   * @param table .
   */
  public void controlReard(HorseRaceTable table) {
    boolean win = controlScript.controlReward(table);
    long earn = 0;
    for (;;) {
      randArea(table);
      earn = table.playerTotalBets()
          - table.playerareaBets(table.getHorse(table.horse)) * table.getAmapMult(table.horse);
      if (earn > 0 && win) {
        break;
      }
      if (earn < 0 && !win) {
        break;
      }
      if (earn == 0) {
        break;
      }
    }
    table.earn = earn;
    msgMgr.sendReward(table);

  }

  /**
   * 根据配置随机配置中一种方案 .
   * 
   * @return
   */
  private int randomPlan() {
    int rand = RandCodeUtil.random(horceLogTypeDic.getTotalWeight());
    int sum = 0;
    for (HorceraceLogTypeDomain typeDomain : horceLogTypeDic.list()) {
      sum += typeDomain.getPlanProbability();
      if (rand < sum) {
        return typeDomain.getPlanId();
      }
    }
    return 1;
  }

  /**
   * 随机倍率 .
   * 
   * @param table .
   */
  public void randMultiple(HorseRaceTable table) {
    int planId = randomPlan();
    List<HorseLogPlanDomain> planDomain = new ArrayList<>();
    for (HorseLogPlanDomain domain : horsePlanDic.list()) {
      if (domain.getType() == planId) {
        planDomain.add(domain);
      }
    }
    table.plan = planDomain;
    Collections.shuffle(planDomain);
    LinkedList<HorseLogPlanDomain> rateList = new LinkedList<>(planDomain);
    for (AreBet areBet : table.areBetMap.values()) {
      HorseLogPlanDomain domain = rateList.removeFirst();
      areBet.setMultiple(domain.getRate());
      areBet.setAreId(domain.getAreid());
    }
    msgMgr.sendMultiple(table);

  }

  /**
   * 结算(只是结算筹码，并未告知客户端) .
   * 
   * @param table . //
   */
  private void balanceTable(HorseRaceTable table) {
    int multiple = table.getAmapMult(table.horse);
    for (HorseRaceSeat seat : table.seats) {
      if (seat.getPlayerId() == 0) {
        continue;
      }
      seat.billChips = seat.bets.getOrDefault(table.getHorse(table.horse), 0L) * multiple;
      // LOG.info("玩家结算房间[{}]开出结果[{}]----------------------------------[{}]",
      // table.room.id, table.horse,
      // seat.billChips);
    }
    controlScript.balanceControl(table);
  }

  /**
   * 查找空位置, 优先查找有人的桌子.
   * 
   * @param player 玩家.
   * @param room .
   * @return
   */
  private HorseRaceSeat findEmptySeat(Player player, HorseRaceRoom room) {
    HorseRaceSeat emptySeat = null;
    ArrayList<HorseRaceTable> tables = new ArrayList<>(room.tables.values());
    Collections.shuffle(tables);

    for (HorseRaceTable table : tables) {
      for (HorseRaceSeat seat : table.seats) {
        if (seat.getPlayerId() == 0) {
          emptySeat = seat;
        }
      }
    }

    return emptySeat;
  }

  /**
   * 玩家下注 .
   * 
   * @param player 玩家.
   * @param area .
   * @param chips .
   */
  public void playerBet(Player player, int area, long chips) {
    HorseRaceSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (area < 1 || area > 15) {
      LOG.error("[赛马]玩家[{}][{}]下注区域[{}]不合法", player.getUserName(), player.getId(), area);
      return;
    }
    if (seat == null) {
      LOG.error("[赛马]玩家[{}][{}]不在牌桌中不能下注", player.getPlayerName(), player.getId());
      return;
    }
    if (seat.table.status != GameStatus.BET) {
      LOG.error("[赛马]非下注阶段玩家[{}][{}]不能下注", player.getPlayerName(), player.getId());
      return;
    }
    if (chips < 0 || chips > seat.getTotalChips()) {
      LOG.error("[赛马]玩家[{}][{}]下注筹码不正确", player.getPlayerName(), player.getId());
      return;
    }

    long areaBet = seat.bets.getOrDefault(area, 0L) + chips;
    seat.bets.put(area, areaBet);

    /*
     * 扣除筹码和金币 .
     */
    seat.setTotalChips(seat.getTotalChips() - chips);
    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);
    playerMgr.minusGold(player, convertGold(seat.table.room.getId().intValue(), chips),
        LogReason.HORSE_RACE_BET);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));

    // 发送下注成功
    msgMgr.sendBetMsg(player, area, chips);
    // 同步桌子下注筹码消息
    msgMgr.sendTableBetInfosMsg(seat.table);

    LOG.info("[赛马]玩家[{}][{}]下注区域[{}][{}]成功", player.getPlayerName(), player.getId(), area, chips);
  }

  /**
   * 清除下注 .
   * 
   * @param player 玩家.
   */
  public void clearBet(Player player) {
    HorseRaceSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[赛马]玩家[{}][{}]没有在桌子中不能清除下注筹码", player.getPlayerName(), player.getId());
      return;
    }

    HorseRaceTable table = seat.table;
    if (table.status != GameStatus.BET) {
      LOG.error("[赛马]玩家[{}][{}]不是下注阶段不能清除下注", player.getPlayerName(), player.getId());
      msgMgr.sendClearBetMsg(player, 1);
      return;
    }

    // 合计下注
    long totalBets = seat.totalBets();
    seat.bets.clear();
    msgMgr.sendClearBetMsg(player, 0);

    seat.setTotalChips(seat.getTotalChips() + totalBets);
    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);
    playerMgr.addGold(player, convertGold(seat.table.room.getId().intValue(), totalBets),
        LogReason.HORSE_CLEAR_BET);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));

    msgMgr.sendTableBetInfosMsg(seat.table);
    LOG.info("[赛马]玩家[{}][{}]清除下注成功", player.getPlayerName(), player.getId());
  }

  /**
   * 随机场景 .
   * 
   * @return
   */
  private int getSenceId() {
    int size = horseraceSenceTypeDic.list().size();
    int index = RandCodeUtil.random(1, size - 1);
    int senceId = horseraceSenceTypeDic.list().get(index).getSenceId();
    return senceId;
  }

  /**
   * 座位结算 .
   * 
   * @param seat 座位信息.
   */
  private void doBill(HorseRaceSeat seat) {
    long playerId = seat.getPlayerId();
    int roomId = seat.table.room.getId().intValue();
    // 扣除输赢
    seat.setTotalChips(seat.getTotalChips() + seat.billChips);
    long billGold = convertGold(roomId, seat.billChips);

    /*
     * 游戏结算金币 .
     */
    Player player = playerMgr.getPlayer(playerId);
    PlayerBo playerDom = playerMgr.selectPlayer(playerId);
    if (player != null) {
      playerMgr.addGold(player, billGold, LogReason.HORSE_RACE_BILL);
    } else {
      playerMgr.addGold(playerDom, billGold, LogReason.HORSE_RACE_BILL);
    }
    // 输赢结果
    long winChips = seat.billChips - seat.totalBets();
    long winGold = convertGold(roomId, winChips);

    LOG.info("[赛马]]]玩家[{}][{}]结算筹码[{}]",
        player != null ? player.getPlayerName() : playerDom.getPlayerName(),
        player != null ? player.getId() : playerDom.getId(), winChips);

    DbLogService
        .log(new Game16WinLoseLog(playerDom, seat, winChips, winGold, LogReason.HORSE_RACE_BILL));

    msgMgr.sendBanlance(seat);
    if (seat.billChips > 0) {
      msgMgr.sendedNotice(seat.getPlayerName(), seat.table.horse, seat.billChips);
    }
    seat.resetGameData();
    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);
    // eventMgr.post(new
    // RoomMemInfoUpdateEvent(playerMgr.getPlayer(playerId)));
  }

  /**
   * 清除玩家,服务器出现bug，玩家卡在游戏中，后台可以清除玩家 .
   * 
   * @param playerId 玩家id.
   */
  private void doClearGameData(long playerId) {
    try {
      dataMgr.removePlayerSeat(playerId);
      /*
       * 清除玩家房间数据 .
       */
      for (HorseRaceRoom room : dataMgr.rooms()) {
        room.getPlayers().remove(playerId);
        /*
         * 清除房间中玩家所在桌子数据 .
         */
        for (HorseRaceTable table : room.tables.values()) {
          for (HorseRaceSeat seat : table.seats) {
            if (seat.getPlayerId() == playerId) {
              seat.clear();
              Player player = playerMgr.getPlayer(playerId);
              if (player != null) {
                loginMgr.noticeLogoutForGameExit(player);
              }
            }
          }
        }
      }

      Player player = playerMgr.getPlayer(playerId);
      if (player != null) {
        loginMgr.noticeLogoutForGameExit(player);
      }
    } catch (Exception e) {
      LOG.error("[赛马]清除玩家[" + playerId + "]卡限失败", e);
    }
  }
}
